package com.ruben.service.impl;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.aliyuncs.utils.StringUtils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruben.constant.UserConstant;
import com.ruben.dao.MpOrderMapper;
import com.ruben.dao.MpUserInfoMapper;
import com.ruben.dao.MpUserMapper;
import com.ruben.feign.ConsumerService;
import com.ruben.pojo.User;
import com.ruben.pojo.bo.UserBO;
import com.ruben.pojo.dto.PageDTO;
import com.ruben.pojo.po.UserInfoPO;
import com.ruben.pojo.po.UserPO;
import com.ruben.pojo.to.UserTO;
import com.ruben.service.UserService;
import com.ruben.utils.AjaxJson;
import com.ruben.utils.Encrypt;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @ClassName: UserServiceImpl
 * @Description:
 * @Date: 2020/8/22 21:17
 * *
 * @author: achao<achao1441470436 @ gmail.com>
 * @version: 1.0
 * @since: JDK 1.8
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    /*@Resource
    private UserDao userDao;*/
    @Resource
    private MpUserMapper mpUserMapper;
    @Resource
    private MpUserInfoMapper mpUserInfoMapper;
    @Resource
    private MpOrderMapper mpOrderMapper;
    @Resource
    private ConsumerService consumerService;

    /**
     * 校验登录用户
     *
     * @param user
     * @return
     */
    @Override
    public UserPO validateUser(User user) {
        String username = user.getUsername();
        UserPO userFromServer = getUserByUsername(username);
        if (userFromServer == null) {
            return null;
        }
        //存入缓存
        stringRedisTemplate
                .opsForHash()
                .put(UserConstant.USER_CACHE,
                        UserConstant.LOGIN_USER_PRE + user.getUsername(),
                        JSON.toJSONString(userFromServer));
        //密码校验(对明文密码进行加密，然后比对数据库中加密的密码，这里加密规则为SALT+用户名+密码的字符串再进行SHA512加密)
        String password = Encrypt.SHA512(UserConstant.SALT + user.getUsername() + user.getPassword());
        if (!password.equals(userFromServer.getPassword())) {
            return null;
        }
        return userFromServer;
    }

    @Override
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public UserPO getUserByUsername(String username) {
        User condition = new User();
        condition.setUsername(username);
        //从缓存中获取，获取不到再从数据库中获取
        String userJson = (String) stringRedisTemplate
                .opsForHash()
                .get(UserConstant.USER_CACHE,
                        UserConstant.LOGIN_USER_PRE + username);
        if (StringUtils.isEmpty(userJson)) {
            //从数据库中获取
            return mpUserMapper.selectOne(Wrappers.lambdaQuery(UserPO.builder().username(username).build()));
        }
        return JSON.parseObject(userJson, UserPO.class);
    }

    @Override
    public String createToken(UserPO user, int expire) {
        String token = JWT.create()
                .withClaim("username", user.getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis() + expire))
                .sign(Algorithm.HMAC256(user.getPassword()));
        stringRedisTemplate.opsForValue().set(UserConstant.TOKEN + token, token);
        stringRedisTemplate.expire(UserConstant.TOKEN + token, expire, TimeUnit.MILLISECONDS);
        return token;
    }

    @Override
    public String getToken(HttpServletRequest request) {
        AtomicReference<String> token = new AtomicReference<>(Optional.ofNullable(request.getParameter("token"))
                .orElse(request.getHeader("token")));
        if (StringUtils.isEmpty(token.get())) {
            Optional.ofNullable(request.getCookies()).ifPresent(cookies -> {
                for (Cookie cookie : cookies) {
                    if ("token".equals(cookie.getName())) {
                        try {
                            token.set(URLDecoder.decode(cookie.getValue(), "utf-8"));
                        } catch (UnsupportedEncodingException e) {
                            log.error("cookie转换错误", e);
                        }
                    }
                        }
                    }
            );
        }
        return token.get();
    }

    @Override
    public boolean logout(String token) {
        try {
            String username = getUsernameByToken(token);
            //清除缓存
            stringRedisTemplate
                    .opsForHash()
                    .delete(UserConstant.USER_CACHE,
                            UserConstant.LOGIN_USER_PRE + username);
            stringRedisTemplate
                    .opsForHash()
                    .delete(UserConstant.TOKEN + token);
        } catch (Exception e) {
            log.error("注销失败，记录日志", e);
            return false;
        }
        return true;
    }

    @Override
    public AjaxJson refreshToken(String refreshToken) {
        try {
            UserPO user = verifyToken(refreshToken);
            if (user == null) {
                return AjaxJson.error("失效的token");
            }
            String token = createToken(user, UserConstant.TOKEN_EXPIRE_TIME);
            long expireTime = JWT.decode(refreshToken).getExpiresAt().getTime();
            if (expireTime - System.currentTimeMillis() <= UserConstant.TOKEN_REFRESH_TIME) {
                refreshToken = createToken(user, UserConstant.REFRESH_TOKEN_EXPIRE_TIME);
            }
            return AjaxJson.success("已成功刷新")
                    .put("token", token)
                    .put("refreshToken", refreshToken);
        } catch (Exception e) {
            log.error("失效的token", e);
            return AjaxJson.error("失效的token");
        }
    }

    @Override
    public UserPO verifyToken(String refreshToken) {
        UserPO user;
        try {
            String token = stringRedisTemplate.opsForValue().get(UserConstant.TOKEN + refreshToken);
            if (StringUtils.isEmpty(token)) {
                return null;
            }
            if (!token.equals(refreshToken)) {
                return null;
            }
            String username = getUsernameByToken(refreshToken);
            user = getUserByUsername(username);
            Algorithm algorithm = Algorithm.HMAC256(user.getPassword());
            JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
            verifier.verify(refreshToken);
        } catch (IllegalArgumentException | JWTVerificationException e) {
            return null;
        }
        return user;
    }

    @Override
    public User getUserByUuid(String uuid) {
        return null;
    }

    @Override
    public User createUser(User user) {
        // 密码加密
        String password = Encrypt.SHA512(UserConstant.SALT + user.getUsername() + user.getPassword());
        UserPO userPO = UserPO.builder()
                .id(new Long(System.currentTimeMillis()).intValue())
                .username(user.getUsername())
                .password(password)
                .build();
        try {
            int affect = mpUserMapper.insert(userPO);
            if (affect != 1) {
                return null;
            }
            return user;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public UserPO findUserByUsername(String username) {
        return mpUserMapper.selectOne(Wrappers.lambdaQuery(UserPO.builder().username(username).build()));
    }

    @Override
    @Transactional
    public String register(User user) {
        // 密码加密
        String password = Encrypt.SHA512(UserConstant.SALT + user.getUsername() + user.getPassword());
        UserPO userPO = UserPO.builder()
                .id(new Long(System.currentTimeMillis()).intValue())
                .username(user.getUsername())
                .password(password)
                .build();
        try {
            // 插入用户
            int affect = mpUserMapper.insert(userPO);
            if (affect != 1) {
                return "添加用户失败";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "添加用户失败";
        }
        return null;
    }

    @Override
    public IPage<UserPO> findList(PageDTO pageDTO) {
        return mpUserMapper.selectPage(new Page<>(pageDTO.getPageNum(), pageDTO.getPageSize()), Wrappers.lambdaQuery());
    }

    @Override
    public UserTO findUserList(UserTO param) {
        // 这里查询条件使用PO传输
        IPage<UserPO> userPage = mpUserMapper.selectPage(new Page<>(param.getPageNum(), param.getPageSize()),
                Wrappers.lambdaQuery(UserPO.builder().username(param.getUsername()).build()));
        // 获取用户ids
        List<Integer> userIds = userPage.getRecords().stream().map(UserPO::getId).collect(Collectors.toList());
        Map<Integer, UserInfoPO> userInfoMap = new HashMap<>(userIds.size());
        if (!userIds.isEmpty()) {
            // 查询userInfo并放入userInfoMap，key为id，value为它本身
            List<UserInfoPO> userInfoList = mpUserInfoMapper.selectList(Wrappers.lambdaQuery(UserInfoPO.builder()
                    .build()).in(UserInfoPO::getId, userIds));
            if (!userInfoList.isEmpty()) {
                userInfoMap.putAll(userInfoList.stream()
                        .collect(Collectors.toMap(UserInfoPO::getId, Function.identity())));
            }
        }
        // 封装BO
        List<UserBO> userBOList = userPage.getRecords().stream().map(user -> {
            UserInfoPO userInfo = userInfoMap.getOrDefault(user.getId(), new UserInfoPO());
            return UserBO.builder().user(user).userInfo(userInfo).build();
        }).collect(Collectors.toList());
        param.setTotal(userPage.getTotal());
        param.setDataList(userBOList);
        return param;
    }

    @Override
    public String getUsernameByToken(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    @Override
    @Transactional
    @GlobalTransactional
    public AjaxJson order() {
        try (Entry entry = SphU.entry("resourceName")) {
            consumerService.dropWare();
//        mpOrderMapper.insert(OrderPO.builder().id(1L).build());
        } catch (BlockException e) {
            return AjaxJson.error("慢点");
        }
        return AjaxJson.success();
    }
}
